ELF
今天要來實作ELF Loader,那什麼是ELF呢,我們又為什麼要做ELF Loader呢?
ELF指的是可執行與可鏈結格式(Executable and Linkable Format),是一種常見的二進制文件格式,用於在Linux和Unix等作業系統中存儲可執行程序、共享庫(動態連結庫)和核心轉儲文件。ELF 格式是這些作業系統上的主要可執行文件格式,它提供了一個標準的結構,用於將代碼和數據組織成一個可執行的程序或共享庫。
簡單來說,所謂的ELF就是在Unix的世界中,我們Windows所常見的OOO.exe這種檔案類型。那為什麼我們要實作ELF Loader的答案也呼之欲出-我們要讓模擬器具備直接開啟ELF,執行的能力。
我們今天就是要來實作這個功能。
ELF Loader
那具體而言要怎麼實做呢,我們將處理器要執行一個程式需要的資料分為指令(instruction)以及資料(Data);以指令來說,處理器來在執行的時候會有Program Counter(PC),PC的位置即為我們所要執行的指令所在的記憶體位置。而以資料來說,我們通常是透過跟記憶體相關的指令,例如Load, Store來讀取,而讀取時我們也會知道其所在的記憶體位置。結合上面兩點,我們可以得知無論是指令還是資料,我們只要把它放到記憶體就好了。
那我們如果要這麼做,是直接fopen(elf)之後將他放進一個記憶體空間這麼簡單嗎? 答案是否定的,ELF本身除了指令及資料以外,還包含了給Linker等工具的資訊 (如下圖,只有Loadable的部分是我們需要的),而這些資訊實際上是處理器所不需要的,因此我們要有能力區分,僅將我們需要的資料放到記憶體中。(非常推薦閱讀"程式設計師的自我修養",對於ELF的理解有很大的幫助)
ELF Loader 實作
首先我們根據TDD的原理,我們要先寫一個測試。測試的輸入為一個elf file的路徑,而輸出為該file的entry point (第一道指令位置)。
要怎麼知道該elf的entry point為誰呢,我們可以透過readelf來看,
riscv64-unknown-elf-readelf -a HelloWorld.elf > HelloWorld.readelf
以我產生的HelloWorld為例,我們看到entry point為0x10116,如下圖。
因此我們的Test內容如下
TEST(MyTestSuite, ELFLoaderTest) {
const char* test = "test.elf";
ALISS::loadElf(test);
EXPECT_EQ(ALISS::pc, 0x10116);
}
當loadELF執行完後,我們應該將PC位置設定為0x10116,而EXPECT_EQ就是會去測兩者輸入是否相等,而目前測試結果當然是錯誤,在這邊就不呈現。
接下來我們來實作loadElf的第一個功能,讀Entry point,程式碼如下:
#include <elf.h>
void ALISS::loadElf(const char* filename)
{
// ELF loader function
std::ifstream file(filename, std::ios::binary);
if (!file) {
std::cerr << "Failed to open file: " << filename << std::endl;
return;
}
// Read the ELF header
Elf64_Ehdr elfHeader;
file.read(reinterpret_cast<char*>(&elfHeader), sizeof(Elf64_Ehdr));
// Check ELF magic number
if (memcmp(elfHeader.e_ident, ELFMAG, SELFMAG) != 0) {
std::cerr << "Not a valid ELF file: " << filename << std::endl;
return;
}
// Check ELF class (32-bit or 64-bit)
if (elfHeader.e_ident[EI_CLASS] != ELFCLASS64) {
std::cerr << "Only 64-bit ELF files are supported: " << filename << std::endl;
return;
}
// Check ELF data encoding (little-endian or big-endian)
if (elfHeader.e_ident[EI_DATA] != ELFDATA2LSB) {
std::cerr << "Only little-endian ELF files are supported: " << filename << std::endl;
return;
}
// Get the entry point address
Elf64_Addr entryPoint = elfHeader.e_entry;
ALISS::pc = entryPoint;
// Add more code here to load and work with program segments, sections, etc.
file.close();
return;
}
這段code會先檢查magic number (判斷是否是ELF file),判斷格式後將pc讀出來,由於我們使用elf.h,entry point的位置就在elfHeader.e_entry,非常容易得到。
實現後再重新進行測試,確認測試通過,送出。
碎碎念 : 今天大概介紹 elf file是什麼,並套用elf.h得到了entry point,明天來將Loadable的部分取出來。